package request

import (
	"context"
	"net/url"
	"strings"
	"sync"
	"time"

	"gitee.com/arjunxw/iothub-south-client/service"
	"gitee.com/arjunxw/iothub-south-client/util"
)

func (req *request) getDalInfo(ctx context.Context, dalId string) (dalInfo *service.DalInfo) {
	return req.getDalInfo2(ctx, dalId, false)
}

type call struct {
	wg     sync.WaitGroup
	result *service.DalInfo
	err    error
}

type group struct {
	mu   sync.Mutex
	call *call
}

func (g *group) do(fn func() (*service.DalInfo, error)) (*service.DalInfo, error) {
	g.mu.Lock()
	if c := g.call; c != nil {
		g.mu.Unlock()
		c.wg.Wait()
		return c.result, c.err
	}
	c := new(call)
	c.wg.Add(1)
	g.call = c
	g.mu.Unlock()

	c.result, c.err = fn()
	c.wg.Done()

	g.mu.Lock()
	g.call = nil
	g.mu.Unlock()

	return c.result, c.err
}

var authGroup = &group{}

func (req *request) getDalInfo2(ctx context.Context, dalId string, force bool) (dalInfo *service.DalInfo) {
	dalInfo = req.dalInfoService.FindByDalId(ctx, dalId)
	if dalInfo == nil {
		return nil
	} else {
		dalInfo, _ = authGroup.do(func() (dInfo *service.DalInfo, err error) {
			var result *AuthResult
			if strings.TrimSpace(dalInfo.AccessToken) != "" && !force {
				if time.Now().Unix()-dalInfo.LastRequestTime > int64(dalInfo.ExpiresIn-10) {
					result = req._refreshToken(dalInfo.RefreshToken)
					if result == nil || result.Code != Ok {
						result = req._auth(dalInfo.DalID, dalInfo.Secret)
					}
				}
			} else {
				result = req._auth(dalInfo.DalID, dalInfo.Secret)
			}

			if result != nil {
				if result.Code != Ok {
					// err = errors.New("")
					return
				}
				dalInfo.AccessToken = result.Body.AccessToken
				dalInfo.RefreshToken = result.Body.RefreshToken
				dalInfo.ExpiresIn = result.Body.GetExpiresIn()
				dalInfo.Scope = result.Body.Scope
				dalInfo.LastRequestTime = time.Now().Unix()
				dalInfo.TokenType = result.Body.TokenType
				if err = req.dalInfoService.Update(ctx, dalInfo); err != nil {
					return
				}
			}

			dInfo = dalInfo
			return
		})

		return dalInfo
	}
}

func (req *request) refreshToken(ctx context.Context, dalId string, refreshToken string) (dalInfo *service.DalInfo) {
	dalInfo = req.dalInfoService.FindByDalId(ctx, dalId)
	if dalInfo == nil {
		return nil
	} else {
		dalInfo, _ = authGroup.do(func() (*service.DalInfo, error) {
			if result := req._refreshToken(refreshToken); result != nil && result.Code == Ok {
				dalInfo.AccessToken = result.Body.AccessToken
				dalInfo.RefreshToken = result.Body.RefreshToken
				dalInfo.ExpiresIn = result.Body.GetExpiresIn()
				dalInfo.Scope = result.Body.Scope
				dalInfo.LastRequestTime = time.Now().Unix()
				dalInfo.TokenType = result.Body.TokenType
				if err := req.dalInfoService.Update(ctx, dalInfo); err != nil {
					return nil, err
				}
				return dalInfo, nil
			}
			return nil, nil
		})
		return dalInfo
	}
}

func dalInfoToAuthResult(dalInfo *service.DalInfo) *AuthResult {
	if dalInfo == nil {
		return nil
	}
	return &AuthResult{
		Body: authResultBody{
			AccessToken:  dalInfo.AccessToken,
			RefreshToken: dalInfo.RefreshToken,
			ExpiresIn:    dalInfo.ExpiresIn,
			Scope:        dalInfo.Scope,
			TokenType:    dalInfo.TokenType,
		},
	}
}

func (req *request) _auth(dalId, secret string) *AuthResult {
	result := AuthResult{}
	if err :=
		util.HttpPostForm(AuthUrl(req.host), url.Values{"dalId": {dalId}, "secret": {secret}}, &result); err != nil {
		return nil
	}

	return &result
}

func (req *request) _refreshToken(refreshToken string) *AuthResult {
	result := AuthResult{}
	if err :=
		util.HttpPostForm(RefreshTokenUrl(req.host), url.Values{"refreshToken": {refreshToken}}, &result); err != nil {
		return nil
	}
	return &result
}
